#include <GameEngine/Graphics/framebuffer_object.h>

#include <GameEngine/Graphics/openGL.h>



#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

GAMEENGINE_IMPLEMENT_RTTI(FramebufferObject)


//! Default constructor
FramebufferObject::FramebufferObject() : id_(0), GraphicResource() {
	create_id();
}

//! Copy constructor
/*!
 @param rhs The framebuffer object to copy
 */
FramebufferObject::FramebufferObject(const FramebufferObject& rhs) :
	id_(rhs.id_), render_buffer_(rhs.render_buffer_), texture_(rhs.texture_), GraphicResource()
{
	if ( id_ ) ResourceManager<ID,CONTEXTID>::instance()->reference_resource(*this);
}

//! Destructor
FramebufferObject::~FramebufferObject() {
	clean_id();
}

//! Assignment operator
/*!
 @param rhs The framebuffer object to copy
 */
FramebufferObject& FramebufferObject::operator=(const FramebufferObject& rhs) {
	if ( &rhs != this ) {
		ResourceManager<ID,CONTEXTID>* manager = (ResourceManager<ID,CONTEXTID>*)ResourceManager<ID,CONTEXTID>::instance();
		if ( id_ ) manager->unreference_resource(*this);
		id_ = rhs.id_;
		if ( id_ ) manager->reference_resource(*this);
		render_buffer_ = rhs.render_buffer_;
		texture_ = rhs.texture_;
	}
	return *this;
}

//! Check if the framebuffer object has been allocated
/*!
 @return True if the framebuffer object has been allocated, false otherwise
 */
bool FramebufferObject::initialized() const {
	return ( id_ != 0 );
}

//! Try to bind the framebuffer object
/*!
 @return True if framebuffer object could be bound, false otherwise
 */
bool FramebufferObject::bind_buffer() const {
	if ( id_ == 0 ) return false;
	if ( glBindFramebufferEXT == nil ) return false;
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id_);
	bool res = render_buffer_.bind_buffer();
	res &= texture_.bind_texture();
	return res;
}

//! Try to unbind the framebuffer object
/*!
 @return True if framebuffer object could be unbound, false otherwise
 */
bool FramebufferObject::unbind_buffer() const {
	if ( glBindFramebufferEXT == nil ) return false;
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	bool res = render_buffer_.unbind_buffer();
	res &= texture_.unbind_texture();
	return res;
}

//! Try to setup the size of the framebuffer object
/*!
 *  Framebuffer should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if framebuffer is bound or not.
 *
 @return True if the size of the framebuffer object could be setup, false otherwise
 */
bool FramebufferObject::set_size(RenderbufferObject::RenderbufferSize width, RenderbufferObject::RenderbufferSize height) {
	// Setup the render buffer
	bool res = render_buffer_.bind_buffer();
	res &= render_buffer_.set_storage(width, height, RenderbufferObject::DepthFormat);
	if ( glFramebufferRenderbufferEXT == nil ) res = false;
	else glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_buffer_.id());
	res &= render_buffer_.unbind_buffer();

	// Setup the texture
	res &= texture_.bind_texture();
	res &= texture_.set_parameters(Texture::ReplaceMode);
	res &= texture_.set_parameters(Texture::LinearMagFilter);
	res &= texture_.set_parameters(Texture::LinearMinFilter);
	res &= texture_.set_parameters(Texture::ClampToBorderMode, Texture2d::AllWrapParameter);
	glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
	if ( glFramebufferTexture2DEXT == nil ) res = false;
	else glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texture_.id(), 0);
	res &= texture_.unbind_texture();

	return res;
}

//! Query the status of the framebuffer object
/*!
 *  Framebuffer should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if framebuffer is bound or not.
 *
 @return The status of the framebuffer object
 */
FramebufferObject::Status FramebufferObject::get_status() const {
	if ( glCheckFramebufferStatusEXT == nil ) return UndefinedStatus;
	return (Status)glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
}

//! Accessor to the texture of the framebuffer object (read-write)
/*!
 @return A reference to the texture of the framebuffer object
 */
Texture2d& FramebufferObject::texture() {
	return texture_;
}

//! Accessor to the texture of the framebuffer object (read only)
/*!
 @return A constant reference to the texture of the framebuffer object
 */
const Texture2d& FramebufferObject::texture() const {
	return texture_;
}

//! Clean the previous ID
void FramebufferObject::clean_id() {
	// Nothing to do if ID is not allocated
	if ( id_ == 0 ) return;

	// Unreference ID from the resource manager
	ResourceManager<ID,CONTEXTID>::instance()->unreference_resource(*this);

	// Clean ID member variable
	id_ = 0;
}

//! Allocate a new ID
void FramebufferObject::create_id() {
	// Clean previous ID, if any
	clean_id();

	// Generate a new framebuffer object ID
	glGenFramebuffersEXT(1, &id_);

	// Reference this ID
	ResourceManager<ID,CONTEXTID>::instance()->reference_resource(*this);
}

//! Desallocate the framebuffer object ID
void FramebufferObject::destroy_resource() const {
	glDeleteFramebuffersEXT(1, &id_);
}

//! Return the framebuffer object context
FramebufferObject::CONTEXTID FramebufferObject::context() const {
	return rtti_id_;
}

//! Return the framebuffer object reference
FramebufferObject::ID FramebufferObject::reference() const {
	return id_;
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
